package semantics;

////import com.modeliosoft.modelio.javadesigner.annotations.objid;
import java.util.HashMap;
import java.util.Map;
import parameters.Parameters;
import syntax.Clock;
import syntax.ClockConditionAtom;
import syntax.ClockGuard;
import syntax.Location;
import syntax.Message;
import syntax.Operator;
import syntax.Repository;
import syntax.TIOA;
import syntax.Transition;

////@objid ("004eb6a0-577b-10dc-ac48-082e5f2773a2")
public class State {
    ////@objid ("008c8afc-578f-10dc-ac48-082e5f2773a2")

    private Location location;
    ////@objid ("008362b0-57f3-10dc-ac48-082e5f2773a2")
    private HashMap<Clock, Float> valuation;
    
    private RunStep lastRunStep;
    
    private float bestDelay = 0;

    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }

    public HashMap<Clock, Float> getValuation() {
        return valuation;
    }

    public void setValuation(HashMap<Clock, Float> valuation) {
        this.valuation = valuation;
    }

    public boolean equals(Object o) {
        return equals((State) o);
    }

    
    public boolean equals(State state) {
        boolean res = location.equals(state.location);
        if (!res) {
            return false;
        }

        for (Map.Entry<Clock, Float> entry : valuation.entrySet()) {
            Clock clock = entry.getKey();
            Float value = entry.getValue();
            res = (state.getValuation().get(clock).equals(value));
            if (!res) {
                return false;
            }
        }
        return res;
    }

    private boolean isModel(ClockConditionAtom atom) {
        boolean res = false;
        Float v_x = valuation.get(atom.getClock());
        res = atom.isSatisfied(v_x);
        return res;
    }

    public State(Location location) {
        this.valuation = new HashMap<Clock, Float>();
        this.location = location;
    }

    public State() {
        valuation = new HashMap<Clock, Float>();
        location = null;
        lastRunStep = null;
    }

    public boolean isModel(ClockGuard guard) {
        boolean res = true;
        if (guard == null) {
            return true;
        }
        for (ClockConditionAtom atom : guard.getConditionAtom()) {
            res = isModel(atom);
            if (!res) {
                return false;
            }
        }
        return true;
    }
    
    public boolean isExecutable(Transition transition) {
        boolean res = true;
        State newState = computeNewStateWithoutReset(transition, getDelay(transition));
        res = newState.isModel(transition.getClockGuard());
        return res;
    }
    
    
    public float getDelay(Transition transition) {
        float res = 0f;
        if (transition.getMessage().isInternal()) {
            res = 0f;
        }
        else if (transition.getMessage().isOutput()) {
            res = maxDelay(transition);
        } else {
            res = minDelay(transition);
        }
        return res;
    }
        
   
    public Float minDelay(Transition transition) {
        float res = 0F;
        float v_x;
        float delay = 0F;

        ClockGuard guard = transition.getClockGuard();
        if (guard == null) {
            return 0F;
        }
        if (guard.isEmpty()) {
            return 0F;
        }
        for (ClockConditionAtom atom : guard.getConditionAtom()) {
            v_x = getValuation().get(atom.getClock());
            if (atom.getLowerRelation() == Operator.Op.getLT()) {
                delay = atom.getDomain().getL_b() - v_x + Parameters.getEpsilon();
            } else {
                delay = atom.getDomain().getL_b() - v_x;
            }
            if (delay > res) {
                res = delay;
            }
        }
        return res;
    }

    
    public Float maxDelay(Transition transition) {
        float res = Parameters.getInfinity();
        float v_x;
        float delay = 0F;

        ClockGuard guard = transition.getClockGuard();
        if (guard == null) {
            return res;
        }
        if (guard.isEmpty()) {
            return res;
        }
        
        for (ClockConditionAtom atom : guard.getConditionAtom()) {
            v_x = getValuation().get(atom.getClock());
            if (atom.getUpperRelation() == Operator.Op.getLT()) {
                delay = atom.getDomain().getU_b() - v_x - Parameters.getEpsilon();
            } else {
                delay = atom.getDomain().getU_b() - v_x;
            }
            if (delay < res) {
                res = delay;
            }
        }
        return res;
    }
    
    
    public State computeNewState(Transition transition) {
        float delay = getDelay(transition);
        return computeNewState(transition, delay);
    }
    
    
    public State computeNewState(Transition transition, Float delay) {
        State state = computeNewStateWithoutReset(transition, delay);
        state.applyReset(transition);
        return state;
    }
    
    public State computeNewStateWithoutReset(Transition transition, Float delay) {
        State state = new State();
        state.setLocation(transition.getDestLocation());

        for (Map.Entry<Clock, Float> entry : valuation.entrySet()) {
            float newValue = Math.min(entry.getValue() + delay, Parameters.getInfinity());
            state.getValuation().put(entry.getKey(), newValue);
        }
        
        return state;
    }

    public void applyReset(Transition transition) {
        for (Clock clock : transition.getResetClocks()) {
            this.getValuation().put(clock, 0F);
        }
    }
    
    public void initializeState(Repository repository, Location location) {
        this.setLocation(location);
        for (Clock clock : repository.getAllClocks()) {
            this.getValuation().put(clock, 0F);
        }
        this.setBestDelay(0);
    }

    public static State initializeState(TIOA tioa) {
        Location initialLocation = tioa.getInitialLocation();
        State initialState = new State();
        initialState.setLocation(initialLocation);
        initialLocation.setReachable(true);
        for (Clock clock : tioa.getRepository().getAllClocks()) {
            initialState.getValuation().put(clock, 0F);
        }
        return initialState;
    }
    
    
    @Override
    protected Object clone() {
        State res = null;
        res = new State(location);
        res.setValuation((HashMap<Clock, Float>)(valuation.clone()));
        return res;
    }
    
    

    @Override
    public String toString() {
        return location + "," + valuation.toString();
    }

    @Override
    public int hashCode() {        
        return toString().hashCode();
    }

    public float getBestDelay() {
        return bestDelay;
    }

    public void setBestDelay(float bestDelay) {
        this.bestDelay = bestDelay;
    }

    public RunStep getLastRunStep() {
        return lastRunStep;
    }

    public void setLastRunStep(RunStep lastRunStep) {
        this.lastRunStep = lastRunStep;
    }
    
    
}
